/* Time-stamp: <2009-11-14 15:49:50 xinhaoyuan> */

#define _EKOS_KERNEL_C_

#include <kernel/kernel.h>

#define ATA_TYPE_ATA	1
#define ATA_TYPE_ATAPI	2
#define ATA_TYPE_SATA	3
#define ATA_TYPE_SATAPI	4

#define IDE_BSY			0x80
#define IDE_RDY         0x40
#define IDE_DF          0x20
#define IDE_SRV		    0x10
#define IDE_DRQ		    0x08
#define IDE_ERR         0x01

#define IDE_PORT_DATA   0
#define IDE_PORT_ERR    1
#define IDE_PORT_SCT    2
#define IDE_PORT_S1	    3
#define IDE_PORT_S2	    4  
#define IDE_PORT_S3	    5
#define IDE_PORT_DSEL   6
#define IDE_PORT_SC     7

#define IDE_SEL_MST	    0xA0
#define IDE_SEL_SLV     0xB0

#define IDE_CMD_READ    0x20
#define IDE_CMD_WRITE   0x30
#define IDE_CMD_IDENTIFY	0xEC

#define IDE_OP_READ		0
#define IDE_OP_WRITE	1

#define IDE_CTL_SRST    1 << 2
#define IDE_CTL_NIEN    1 << 1
#define IDE_CTL_HOB     1 << 7

#define SECTOR_SIZE     256

#define ATA_MAX_JOBS	256

#define IDE_IRQ_PRI	    14
#define IDE_IRQ_SEC     15

int ata_cmd_ports[] = {0x1F0, 0x170};
int ata_ctl_ports[] = {0x3F6, 0x376};

#define ATAPI_CMD_IDENTIFY	0xA1

struct ata_drive {
	int present;
	int type;
	int udma_modes;
	int cyls;
	int heads;
	int sectors;
	uint32_t lba28_sectors;
	uint16_t ident[256];
};

struct ata_job {
	int drive;
	uint32_t lba;
	int nsectors;
	int ndone;
	int partdone;
	int partsize;
	int finished;
	int error;
	int op;
	char *data;
	struct ata_job *next;
};

struct ata_ctlr {
	int present;
	int base;
	int cbase;
	struct ata_job jobs[ATA_MAX_JOBS];
	struct ata_job *curjob, *lastjob;
	struct ata_drive drives[2];
};

static struct ata_ctlr ctlrs[2];

static void ide_intr(struct trapframe_t *tf, int nctlr);
int ide_read(int drive, uint32_t lba, int nsectors, char *buf);
int ide_write(int drive, uint32_t lba, int nsectors, char *buf);

static int
read_status(int base)
{
	return inb(base + IDE_PORT_SC);
}

static int
status_error(int status)
{
	return ((status & (IDE_ERR | IDE_DF)) || !(status & IDE_RDY)) ? 1 : 0;
}

static int
select_drive(int base, int drive)
{
	int val;
	if (drive == 0) val = 0xA0;
		else val = 0xB0;
	outb(base + IDE_PORT_DSEL, val);
	delay();
}

static void
reset_ctlr(int cbase)
{
	outb(cbase, IDE_CTL_SRST);
	delay();
	outb(cbase, 0);
}

static int
send_cmd(int base, int cmd)
{
	outb(base + IDE_PORT_SC, cmd);
}

static int
ide_wait(int base)
{
	int r;
	while (((r = read_status(base)) & IDE_BSY) || !(r & IDE_RDY)) ;
	if (r & (IDE_DF | IDE_ERR))
		return -E_IOERR;
	return 0;
}

static int
ata_identify(struct ata_drive *drv)
{
	int i;
/*	kprintf("IDENTIFY result:");
	for (i=0; i<IDENT_COUNT; i++)
	{
		if (i % 16 == 0) kprintf("\n");
		else kprintf(" ");
		kprintf("%04x", drv->ident[i]);
		kprintf("%c%c", drv->ident[i] >> 8, drv->ident[i] & 0xff);
	}
	kprintf("\n");*/
	kprintf("C/H/S: %d %d %d\n", drv->ident[1], drv->ident[3], drv->ident[6]);
	drv->cyls = drv->ident[1];
	drv->heads = drv->ident[3];
	drv->sectors = drv->ident[6];
	kprintf("SN: ");
	for (i=10; i<20; i++)
		kprintf("%c%c", drv->ident[i] >> 8, drv->ident[i] & 0xff);
	kprintf("\n");
	kprintf("Firmware Revision: ");
	for (i=23; i<27; i++)
		kprintf("%c%c", drv->ident[i] >> 8, drv->ident[i] & 0xff);
	kprintf("\n");
	kprintf("Model number: ");
	for (i=27; i<47; i++)
		kprintf("%c%c", drv->ident[i] >> 8, drv->ident[i] & 0xff);
	kprintf("\n");
	drv->lba28_sectors = (drv->ident[61] << 16) + drv->ident[60];
	if (drv->lba28_sectors != 0)
		kprintf("LBA28 sectors: %lu\n", drv->lba28_sectors);
	if (drv->ident[83] & (1 << 10))
	{
		kprintf("LBA48 support: yes\n");
		kprintf("LBA48 sectors: 0x%04x%04x%04x%04x\n", drv->ident[103], drv->ident[102], drv->ident[101], drv->ident[100]);
	}
	if (drv->ident[53] & 1 << 2)
	{
		int udma_sup = drv->ident[88];
		kprintf("UDMA modes support: ");
		for (i=0; i<6; i++)
			if (udma_sup & (1 << i))
				kprintf("%d ", i);
		if (udma_sup == 0)
			kprintf("none");
		kprintf("\n");
		for (i=0; i<6; i++)
			if (udma_sup & (1 << (i+8)))
				kprintf("UDMA mode active: %d\n", i);
		drv->udma_modes = udma_sup & 0xff;
	}

//	cprintf("Press enter to continue.");
//	readline(NULL);
}

static void
read_data(int base, int count, uint16_t *dst)
{
	int i;
	for (i=0; i<count; i++)
	{
		dst[i] = inw(base + IDE_PORT_DATA);
//		delay();
	}
}

static void
write_data(int base, int count, uint16_t *src)
{
	int i;
	for (i=0; i<count; i++)
	{
		outw(base + IDE_PORT_DATA, src[i]);
		delay();
	}
}

static int
ide_op(int base, struct ata_job *job)
{
	int sectors;
	if (job->nsectors - job->ndone > 256)
		sectors = 256;
	else
		sectors = job->nsectors - job->ndone;

	job->partsize = sectors;
	job->partdone = 0;
	int lba = job->lba + job->ndone;

	ide_wait(base);
	outb(base + IDE_PORT_DSEL, 0xE0 | (job->drive << 4) | ((lba >> 24) & 0x0F));
	outb(base + IDE_PORT_ERR, 0x00);
	ide_wait(base);
	outb(base + IDE_PORT_SCT, sectors);
	outb(base + IDE_PORT_S1,  lba & 0xFF);
	outb(base + IDE_PORT_S2,  (lba >> 8) & 0xFF);
	outb(base + IDE_PORT_S3,  (lba >> 16) & 0xFF);
	send_cmd(base, job->op == IDE_OP_READ ? IDE_CMD_READ : IDE_CMD_WRITE);
	return 0;
}

static int
ide_intr0(struct trapframe_t *tf)
{
	ide_intr(tf, 0);
	return 0;
}

static int
ide_intr1(struct trapframe_t *tf)
{
	ide_intr(tf, 1);
	return 0;
}

int
ide_init(void)
{
	int i;

	register_irq_handler(IDE_IRQ_PRI, ide_intr0);
	register_irq_handler(IDE_IRQ_SEC, ide_intr1);

	int ctlr;
	for (ctlr = 0; ctlr < 2; ctlr ++)
	{
		int base = ata_cmd_ports[ctlr];
		int cbase = ata_ctl_ports[ctlr];;

		if (inb(base + IDE_PORT_SC) != 0xFF)
		{
			ctlrs[ctlr].present = 1;
			ctlrs[ctlr].base = base;
			ctlrs[ctlr].cbase = cbase;
			for (i=0; i<ATA_MAX_JOBS-1; i++)
			{
				ctlrs[ctlr].jobs[i].finished = 1;
				ctlrs[ctlr].jobs[i].next = &ctlrs[ctlr].jobs[i+1];
			}
			ctlrs[ctlr].jobs[ATA_MAX_JOBS-1].next = &ctlrs[ctlr].jobs[0];
			ctlrs[ctlr].curjob = NULL;
			ctlrs[ctlr].lastjob = &ctlrs[ctlr].jobs[ATA_MAX_JOBS-1];
			reset_ctlr(cbase);
			char *sctlr = (ctlr == 0 ? "Primary" : "Secondary");
			int drive;
			for (drive = 0; drive < 2; drive ++)
			{
				char *sdrv = (drive == 0 ? "Master" : "Slave");
				select_drive(base, drive);
				send_cmd(base, IDE_CMD_IDENTIFY);
				int res = read_status(base);
				struct ata_drive *drv =
					&ctlrs[ctlr].drives[drive];
				if (res == 0) 
					drv->present = 0;
				else {
					if (ide_wait(base) == 0)
					{
						kprintf("ATA device present at %s %s.\n", sctlr, sdrv);
						drv->present = 1;
						drv->type = ATA_TYPE_ATA;
						read_data(base, SECTOR_SIZE, &drv->ident[0]);
						ata_identify(drv);
					} else 
					if ((inb(base + 4) == 0x14) && (inb(base + 5) == 0xEB))
					{
						kprintf("ATAPI device present at %s %s.\n", sctlr, sdrv);
//						drv->present = 1;
						drv->type = ATA_TYPE_ATAPI;
						send_cmd(base, ATAPI_CMD_IDENTIFY);
						int res = read_status(base);
						read_data(base, SECTOR_SIZE, &drv->ident[0]);
						ata_identify(drv);
					} else
					if ((inb(base + 4) == 0x3C) && (inb(base + 5) == 0xC3))
					{
						kprintf("SATA device present at %s %s.\n", sctlr, sdrv);
//						drv->present = 1;
						drv->type = ATA_TYPE_SATA;
					} else
					if ((inb(base + 4) == 0x69) && (inb(base + 5) == 0x96))
					{
						kprintf("SATAPI device present at %s %s.\n", sctlr, sdrv);
//						drv->present = 1;
						drv->type = ATA_TYPE_SATAPI;
					} else
					{
						kprintf("Unknown ATA device present at "
								"%s %s.\n", sctlr, sdrv);
						kprintf("Port %03x: %02x\n"
								"Port %03x: %02x\n",
								base + 4, inb(base + 4),
								base + 5, inb(base + 5));
					}
				}
			}
		} else ctlrs[ctlr].present = 0;		
	}
	return 0;
}

static int
print_status(int base)
{
	int status = read_status(base);
	kprintf("Status:");
	if (status & IDE_BSY)
		kprintf(" Busy");
	if (status & IDE_RDY)
		kprintf(" Ready");
	if (status & IDE_DF)
		kprintf(" Drive_Fault");
	if (status & IDE_SRV)
		kprintf(" Overlapped_Mode");
	if (status & IDE_DRQ)
		kprintf(" Data_Request");
	if (status & IDE_ERR)
		kprintf(" Error");
	kprintf("\n");
	return status;
}

static void 
ide_intr(struct trapframe_t *tf, int nctlr)
{
	kprintf("IDE interrupt\n");
	struct ata_ctlr *ctlr = &ctlrs[nctlr];
	struct ata_job *job = ctlr->curjob;

	int status = print_status(ctlr->base);
	int donext = 0;
	int newjob = 0;
	if (job == NULL || job->finished)
	{
		// Not read/write
		kprintf("	Other interrupt.\n");
//		readline(NULL);
		return;
	}
	if (status & IDE_DRQ)
	{
		uint16_t *dptr = (uint16_t *)job->data + SECTOR_SIZE * (job->ndone + job->partdone);
		if (job->op = IDE_OP_READ)
			read_data(ctlr->base, SECTOR_SIZE, dptr);
		else if (job->op = IDE_OP_WRITE)
			write_data(ctlr->base, SECTOR_SIZE, dptr);
	}
	if (status_error(status))
	{
		job->error = 1;
		job->finished = 1;
		donext = 1;
		newjob = 1;
	} else
	{
		job->partdone++;
		if (job->partdone == job->partsize)
		{
			job->ndone += job->partsize;
			donext = 1;
			if (job->ndone == job->nsectors)
			{
				// Job finished!
				job->error = 0;
				job->finished = 1;
				newjob = 1;
			}
		}
	}
	if (newjob)
	{
		if (job->next->finished)
		{
			// All job done!
			ctlr->curjob = NULL;
			donext = 0;
		} else {
			ctlr->curjob = job->next;
		}
	}
	if (donext)
	{
		ide_op(ctlr->base, job->next);
	}
//	readline(NULL);
}

static int
ide_doop(int drive, uint32_t lba, int nsectors, char *buf, int op)
{
	if (drive > 3)
		return -E_INVAL;
	if (ctlrs[drive / 2].present == 0)
		return -E_INVAL;
	struct ata_ctlr *ctlr = &ctlrs[drive / 2];
	if (ctlr->drives[drive & 1].present == 0)
		return -E_INVAL;
	struct ata_job *tail = ctlr->lastjob;
	if (tail->next->finished == 0) // queue full
		return -E_NO_MEM;
	ctlr->lastjob = tail->next;
	tail = tail->next;
	tail->drive = drive & 1;
	tail->lba = lba;
	tail->nsectors = nsectors;
	tail->ndone = 0;
	tail->partdone = 0;
	tail->data = buf;
	tail->finished = 0;
	tail->error = 0;
	tail->op = op;
	if (ctlr->curjob == NULL)
	{
		ctlr->curjob = tail;
		ide_op(ata_cmd_ports[drive / 2], tail);
	}
	// Loop until finished
	while (tail->finished == 0) sch_yield(ENV_SCH_WAITING);
	if (tail->error)
		return -E_IOERR;
	else
		return 0;
}

int
ide_read(int drive, uint32_t lba, int nsectors, char *buf)
{
	return ide_doop(drive, lba, nsectors, buf, IDE_OP_READ);
}

int
ide_write(int drive, uint32_t lba, int nsectors, char *buf)
{
	return ide_doop(drive, lba, nsectors, buf, IDE_OP_WRITE);
}

